package com.moweilin.folded_binding;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by moweilin on 2017/1/11.
 */
public class FoldedBinder {
    public static final Integer PAGE_COUNT_PER_PAPER = 4;

    private StringProperty sourceFile = new SimpleStringProperty();
//    private String file;
    private Integer normalPaperCountPerStack;
    private Boolean overlay;
//    private Double bindMargin;
    private TransformMatrix bindMargin;
    private Integer normalPageCountPerStack;

    private List<PaperStack> paperStackList;
    private List<Integer> pageIndexList;
    private Integer pageCount;
    private PDDocument sourceDocument;
    private boolean mask;

    public boolean isMask() {
        return mask;
    }

    public void setMask(boolean mask) {
        this.mask = mask;
    }

    public FoldedBinder(String file, float bindMargin) throws IOException {
        this(file,new TransformMatrix(bindMargin));
    }
    public FoldedBinder(String file,TransformMatrix bindMargin) throws IOException {
        this(5,true,bindMargin,file);
    }
//
//    public FoldedBinder() {
//    }


    public FoldedBinder(String file) throws IOException {
        this(file,0.0f);
    }
    public FoldedBinder(PDDocument sourceDocument,TransformMatrix bindMargin) throws IOException {
        this(5,true,bindMargin,sourceDocument);
    }
    public FoldedBinder(Integer normalPaperCountPerStack, Boolean overlay, TransformMatrix bindMargin, String file) throws IOException {
        this(normalPaperCountPerStack,overlay,bindMargin,PDDocument.load(new File(file)));
    }


    public FoldedBinder(Integer normalPaperCountPerStack, Boolean overlay, TransformMatrix bindMargin, PDDocument sourceDocument) throws IOException {
//        this.file = file;
        this.normalPaperCountPerStack = normalPaperCountPerStack;
        this.overlay = overlay;
        this.bindMargin = bindMargin;
        this.normalPageCountPerStack = normalPaperCountPerStack * PAGE_COUNT_PER_PAPER;
        this.paperStackList = new ArrayList<>();
        this.sourceDocument = sourceDocument;
        pageCount = sourceDocument.getNumberOfPages();
    }

    public String getSourceFile() {
        return sourceFile.get();
    }

    public StringProperty sourceFileProperty() {
        return sourceFile;
    }

    public void setSourceFile(String sourceFile) {
        this.sourceFile.set(sourceFile);
    }

    public void saveToSinglePageFile(String filePath,Integer ... skipMarginPages) throws IOException {
        if(pageIndexList == null){
            build();
        }
        PDDocument newDocument = new PDDocument();

        LayerUtility layerUtility = new LayerUtility(newDocument);
        for (Integer i = 0; i < pageIndexList.size(); i++) {
            Integer index = pageIndexList.get(i);
            if(index >= pageCount){
                PDPage page = new PDPage(sourceDocument.getPage(0).getCropBox());
                newDocument.addPage(page);
            }else {
                PDPage sourcePage = sourceDocument.getPage(index);
                if(mask) {
                    PDPageContentStream sourceStream = new PDPageContentStream(sourceDocument, sourcePage, PDPageContentStream.AppendMode.APPEND,true);
                    appendMark(sourceStream,sourcePage.getTrimBox(),index);
                    sourceStream.close();
                }
                PDRectangle cropBox = sourcePage.getCropBox();
                PDRectangle newRect = new PDRectangle(cropBox.getWidth(),cropBox.getHeight());
                PDPage newPage = new PDPage(newRect);
                newDocument.addPage(newPage);

                PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, index);
                PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
                PDRectangle transRect = new PDRectangle(cropBox.getWidth(),cropBox.getHeight());
                if(!containsIn(index,skipMarginPages)) {
                    float sc = bindMargin.getScale();
                    float transY = cropBox.getLowerLeftY() - cropBox.getHeight() * (sc - 1) / 2;

                    float transX =cropBox.getLowerLeftX() - cropBox.getWidth() * (sc - 1) / 2;
                    if(i % 2 == 0){
                        transX = transX + bindMargin.translateX;
                    }else {
                        transX = transX - bindMargin.translateX;
                    }
                    transRect.setLowerLeftX(transX);
                    transRect.setLowerLeftY(transY);
                    Matrix marginMatrix= Matrix.getTranslateInstance(transX, transY);
                    contentStream.saveGraphicsState();

                    marginMatrix.getTranslateX();
                    marginMatrix.scale(sc, sc);
                    contentStream.transform(marginMatrix);
                }

                contentStream.drawForm(formXObject);
                contentStream.restoreGraphicsState();

                contentStream.close();
            }

        }
        newDocument.save(filePath);
        newDocument.close();
    }

    public int getPageCount() {
        return pageCount;
//        return 100;
    }



    public Integer getNormalPaperCountPerStack() {
        return normalPaperCountPerStack;
    }

    public Boolean getOverlay() {
        return overlay;
    }

    private boolean containsIn(Integer page,Integer[] arrays){
        for (int i = 0; i < arrays.length; i++) {
            if(arrays[i] == page){
                return true;
            }
        }
        return false;
    }

    public List<PaperStack> getPaperStackList() {
        return paperStackList;
    }

    public List<Integer> getPageIndexList() {
        return pageIndexList;
    }
    public void close() throws IOException{
        sourceDocument.close();
    }
    public void build() throws IOException {

        int fillCount = Double.valueOf(Math.ceil(Double.valueOf(pageCount) / Double.valueOf(PAGE_COUNT_PER_PAPER))).intValue() * PAGE_COUNT_PER_PAPER;
        int stackCount = fillCount / normalPageCountPerStack;
        int leftPaperCount = Double.valueOf(Math.ceil((fillCount % normalPageCountPerStack)/PAGE_COUNT_PER_PAPER)).intValue();
        int normalPaperCount = stackCount - leftPaperCount;
        if(normalPaperCount < 0){
            overlay = false;
        }
        if(leftPaperCount > 0 && !overlay){
            stackCount ++;
        }

        for (int i = 1; i <= stackCount; i++) {
            int totalAddCount = 0;
            if(overlay){
                totalAddCount = i - normalPaperCount ;
                if(totalAddCount < 0 ){
                    totalAddCount = 0;
                }
            }
            int endIndex = i * normalPageCountPerStack+ totalAddCount * PAGE_COUNT_PER_PAPER - 1;
            int count = totalAddCount > 0 ? normalPageCountPerStack + PAGE_COUNT_PER_PAPER : normalPageCountPerStack;
            int startIndex = endIndex - count + 1;
            paperStackList.add(new PaperStack(startIndex,count));
        }
        pageIndexList = new ArrayList<>();
        paperStackList.forEach(paperStack -> paperStack.getPaperList().forEach(paper -> {
            pageIndexList.add(paper.getLeftFront());
            pageIndexList.add(paper.getRightFront());
            pageIndexList.add(paper.getRightBack());
            pageIndexList.add(paper.getLeftBack());
        }));
    }



    protected void appendMark(PDPageContentStream stream,PDRectangle rectangle,int page) throws IOException{

            Matrix matrix = Matrix.getTranslateInstance(rectangle.getLowerLeftX(),rectangle.getLowerLeftY());
//            stream.restoreGraphicsState();
            stream.transform(matrix);
            stream.setNonStrokingColor(Color.BLUE);
            stream.setStrokingColor(Color.RED);
            stream.setFont(PDType1Font.HELVETICA,120);
            stream.beginText();
            stream.newLineAtOffset(rectangle.getWidth() / 2, rectangle.getHeight() / 2);
            Integer ind = page + 1;
            stream.showText(ind.toString());
            stream.endText();


            float lineWidth = 4;
            float half = lineWidth / 2;

            stream.setLineWidth(lineWidth);
            stream.moveTo(half, half);
            stream.lineTo(rectangle.getWidth() - half, half);
            stream.lineTo(rectangle.getWidth() - half, rectangle.getHeight() - half);
            stream.lineTo(half, rectangle.getHeight() - half);
            stream.lineTo(half, half);
            stream.lineTo(rectangle.getWidth() - half, rectangle.getHeight() - half);
            stream.moveTo(half, rectangle.getHeight() - half);
            stream.lineTo(rectangle.getWidth() - half, half);
            stream.stroke();

    }

    public static class TransformMatrix {
        private float scale;
        private float translateX;


        public TransformMatrix(float translateX, float scale) {
            this.scale = scale;
            this.translateX = translateX;
        }

        public TransformMatrix(float translateX) {
            this(translateX,1);
        }

        public float getTranslateX() {
            return translateX;
        }

        public void setTranslateX(float translateX) {
            this.translateX = translateX;
        }

        public float getScale() {
            return scale;
        }

        public void setScale(float scale) {
            this.scale = scale;
        }
    }
    public static class PaperStack{

        private int startIndex;
        private int count;
        private int paperCount;
        private List<Paper> paperList;



        /**
         *
         * @param startIndex page start index
         * @param count page count
         */
        public PaperStack(int startIndex, int count) {
            this.startIndex = startIndex;
            this.count = count;
            paperCount = (count % PAGE_COUNT_PER_PAPER > 0) ? count / PAGE_COUNT_PER_PAPER + 1 :count / PAGE_COUNT_PER_PAPER  ;

            paperList = new ArrayList<>();

            for (Integer i = 0; i < paperCount; i++) {
                int leftBack = i * 2 + startIndex;
                int leftFront = leftBack + 1;
                int rightBack = count - i * 2 + startIndex - 1;
                int rightFront = rightBack - 1;
                Paper paper = new Paper(leftFront,leftBack,rightFront,rightBack);
                paperList.add(paper);
            }

        }

        public int getStartIndex() {
            return startIndex;
        }

        public int getCount() {
            return count;
        }

        public Integer getPaperCount() {
            return paperCount;
        }

        public List<Paper> getPaperList() {
            return paperList;
        }
    }

    public static class Paper{
        private Integer leftFront;
        private Integer leftBack;
        private Integer rightFront;
        private Integer rightBack;

        public Paper(Integer leftFront, Integer leftBack, Integer rightFront, Integer rightBack) {
            this.leftFront = leftFront;
            this.leftBack = leftBack;
            this.rightFront = rightFront;
            this.rightBack = rightBack;
        }

        @Override
        public String toString() {
            return "LeftBack: "+ leftBack + "  LeftFront:" + leftFront + "  RightFront:" +  rightFront + "  RightBack:" + rightBack;
        }

        public Integer getLeftFront() {
            return leftFront;
        }

        public Integer getLeftBack() {
            return leftBack;
        }

        public Integer getRightFront() {
            return rightFront;
        }

        public Integer getRightBack() {
            return rightBack;
        }
    }

}
